package cc.mango.util;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStreamReader;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.Set;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

//import org.apache.log4j.Logger;

//
// PropertyFileCache()	:	
//			This class reads a property file [full file name specified], then, put each line
//			with a key and value property to check.  For example, if the file contains IP address...
//
//				127.0.0.1=ok
//				202.3.8.4=blah
//
//			The above example will put the above IP addresses as key, and "ok" and "blah" as its
//			associated values in a cached array.  If the property file has changed, it will read it again.
//			If the property file has not changed, then, upon a request, it will simply
//			pull elements from cache/memory to check.
//
//			This class also allows multiple instance of file to be cached.
//
public class PropertyFileCache
{
	private static final Log				log				= LogFactory
															.getLog(PropertyFileCache.class);
	private static final String			ENCODING			= "UTF-8";							// encoding type...
	private static final String			PARAMETER_DELIMITER	= "=";								// equate character for key and value
	private static final String			COMMENT_DELIMITER	= "#";								//comment delimiter inside the property file
	private static final int				PRINT_ELEMENT_LIMIT	= 50;								// if more than X, do not print all read elements...

	//	private	static	Logger	sysLog = Logger.getLogger(PropertyFileCache.class);

	protected static PropertyFileCache		instance;											// singelton instance

	protected Hashtable<String, FileInfo>	spfCache;											// this stores different property file...

	protected PropertyFileCache()
	{
		spfCache = new Hashtable<String, FileInfo>();

	}

	//
	// getInstance():	get this instance - a singleton
	//
	public static PropertyFileCache getInstance()
	{
		if (instance == null)
		{
			instance = new PropertyFileCache();
		}
		return instance;
	}

	//
	// getAllKeys()	: return an enumeration of all keys
	//
	public Enumeration<String> getAllKeys(String filename)
	{
		Enumeration<String> allkeys = null;
		FileInfo curFileInfo = getFileInfo(filename);
		if (curFileInfo != null)
		{
			checkConf(curFileInfo);
			allkeys = curFileInfo.cachedTokens.keys();
		}
		return allkeys;
	}

	//
	// keySet()	:	return a set for all keys available
	//
	public Set<String> keySet(String filename)
	{
		Set<String> keyset = new HashSet<String>(); // JP, Aug 16, 2006, initialize it to an empty Set
		FileInfo curFileInfo = getFileInfo(filename);
		if (curFileInfo != null)
		{
			checkConf(curFileInfo);
			keyset = curFileInfo.cachedTokens.keySet();
		}
		return keyset;
	}

	//
	// getProperty():	get the who hash table for internal processing
	//
	private Hashtable<String, String> getProperty(String filename)
	{
		Hashtable<String, String> retTable = null;
		FileInfo curFileInfo = getFileInfo(filename);
		if (curFileInfo != null)
		{
			checkConf(curFileInfo);
			retTable = curFileInfo.cachedTokens;
		}
		else
		{
			retTable = new Hashtable<String, String>(); // return empty vector
		}
		return retTable;
	}

	//
	// get():	get the value based on the key and filename
	//
	public String get(String filename, String key)
	{
		String retStr = null;
		FileInfo curFileInfo = getFileInfo(filename);
		if (curFileInfo != null)
		{
			checkConf(curFileInfo);
			retStr = curFileInfo.cachedTokens.get(key);
		}
		return retStr;
	}

	//
	// getTimestamp()	:	return the timestamp of this file.  this is used by external routine to explicit triggers a re-read
	//
	public long getTimestamp(String filename)
	{
		long ts = 0;
		FileInfo curFileInfo = getFileInfo(filename);
		if (curFileInfo != null)
		{
			checkConf(curFileInfo);
			ts = curFileInfo.timestamp;
		}
		return ts;
	}

	//
	// contains()	:	check if the item contains the token
	//
	public boolean contains(String filename, String token)
	{
		boolean status = false;
		FileInfo curFileInfo = getFileInfo(filename);
		if (curFileInfo != null)
		{
			checkConf(curFileInfo);
			status = contains(curFileInfo.cachedTokens, token);
		}
		return status;
	}

	//
	// containsKey()	:	check if key is there!
	//
	public boolean containsKey(String filename, String token)
	{
		boolean status = false;
		FileInfo curFileInfo = getFileInfo(filename);
		if (curFileInfo != null)
		{
			checkConf(curFileInfo);
			status = containsKey(curFileInfo.cachedTokens, token);
		}
		return status;
	}

	//
	// getSize()	:	return the number of items in the file
	//
	public int getSize(String filename)
	{
		int size = 0;
		FileInfo curFileInfo = getFileInfo(filename);
		if (curFileInfo != null)
		{
			checkConf(curFileInfo);
			size = curFileInfo.cachedTokens.size();
		}
		return size;
	}

	//
	// isUpdated()	:	check to see if time stamp has been updated.  This check DOES NOT trigger a re-read!
	//
	public boolean isUpdated(String filename)
	{
		boolean status = false;
		if (filename != null && filename.length() > 0)
		{
			FileInfo curFileInfo = getFileInfo(filename);
			if (curFileInfo != null)
			{
				long newTimestamp = (new File(curFileInfo.filename))
						.lastModified();
				// NOTE: if file does not exist, lastModified() will return '0', let 
				// it proceed to create a zero element HashTable
				if (newTimestamp > curFileInfo.timestamp
						|| newTimestamp == 0)
				{
					status = true; // file has been updated!
				} // else - no need to do anything!
			}
			else
			{
				status = true; // signal update is required because file has not been read before!
			}
		} // else - if file name is invalid, there is nothing to be 'updated', return false...
		return status;
	}

	private FileInfo getFileInfo(String filename)
	{
		FileInfo curFileInfo = null;
		if (filename != null && filename.length() > 0)
		{
			try
			{
				if (spfCache.containsKey(filename))
				{
					curFileInfo = spfCache.get(filename);
				}
				else
				{
					curFileInfo = new FileInfo(filename);
					spfCache.put(filename, curFileInfo);
				}
				curFileInfo = spfCache.get(filename);
			}
			catch (Exception e)
			{
				//				sysLog.error("Cannot read simple property file [" + filename + "]");
			}
		}
		return curFileInfo;
	}

	private String get(Hashtable<String, String> vs, String key)
	{
		return vs.get(key);
	}

	private boolean contains(Hashtable<String, String> vs, String token)
	{
		return vs.contains(token);
	}

	private boolean containsKey(Hashtable<String, String> vs, String key)
	{
		return vs.containsKey(key);
	}

	private void checkConf(FileInfo fi)
	{
		String curDir = null;
		try
		{
			curDir = (new File(".")).getAbsolutePath();
			// if 'fp' is null, meaning cannot read a file, will throw exception
			long newTimestamp = (new File(fi.filename)).lastModified();

			// NOTE: if file does not exist, lastModified() will return '0', let 
			// it proceed to create a zero element HashTable
			if (newTimestamp > fi.timestamp || newTimestamp == 0)
			{
				parseProperties(fi); // similar to Properties.load(), but have to deal with UTF-8!
				fi.timestamp = newTimestamp;
				//				sysLog.info("Parsed from DIR[" + curDir + "] + FILE["+ fi.filename + "] with timestamp =[" + fi.timestamp +"]");
			}
		}
		catch (Exception e)
		{
			//			sysLog.fatal("Cannot read file [" + fi.filename + "] from directory [" + curDir + "]..." + e.getMessage());
		}
	}

	private void parseProperties(FileInfo fi)
	{
		if (fi.filename != null && fi.filename.length() > 0)
		{
			Hashtable<String, String> tokensFromFile = new Hashtable<String, String>();

			try
			{
				BufferedReader br = new BufferedReader(
						new InputStreamReader(new FileInputStream(
								fi.filename), ENCODING));
				String tmpValue; // extracted value
				String tmpKey; // extracted key
				String tmpLine; // the line

				while ((tmpLine = br.readLine()) != null)
				{
					//
					// strip off any comment and trim it down
					//
					tmpLine = tmpLine.replaceAll(
							COMMENT_DELIMITER + ".*$", "").trim();
					int sindex = tmpLine.indexOf(PARAMETER_DELIMITER);

					if (sindex != -1)
					{
						tmpKey = tmpLine.substring(0, sindex).trim();
						tmpValue = tmpLine.substring(sindex + 1).trim();

						if ( /* tmpValue.length() > 0 && */tmpKey
								.length() > 0)
						{ // allow empty string to be a variable...
							tokensFromFile.put(tmpKey, tmpValue);
						} // else ignore the parameters
					} // else ignore the parameters
				}
				br.close();
				//				sysLog.info("Property file [" + fi.filename + "] loaded with " + tokensFromFile.size() + " element(s).");
			}
			catch (Exception e)
			{
				//				sysLog.error("Property file [" + fi.filename + "] cannot be loaded " + e.getMessage());
			}
			finally
			{
				if (tokensFromFile != null)
				{
					if (fi.cachedTokens != null)
					{
						fi.cachedTokens.clear(); // remove old Vector
					}
					fi.cachedTokens = tokensFromFile; // use new table
					if (fi.cachedTokens.size() < PRINT_ELEMENT_LIMIT)
					{ // if there are too many, do not print...
					//						sysLog.debug("Property file containing elements...\n" + fi.cachedTokens.toString());
					} // else - don't bother printing...
				}
			}
		}
		else
		{
			//			sysLog.error("Property file [" + fi.filename + "] is not defined");
		}
	}

	//
	// CAUTION:  Do not try to combine this with SimplePropertyFileCache.java.  THEY ARE DIFFERENT!!!
	//
	final class FileInfo
	{
		private FileInfo(String fn)
		{
			filename = fn;
			// no need to initialize timestamp and cachedTokens, it will be determined later
		}

		private String					filename;
		private long					timestamp;
		private Hashtable<String, String>	cachedTokens;
	}
}